package ar.edu.itba.pod.legajo47573;

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;

import ar.edu.itba.event.EventInformation;
import ar.edu.itba.event.RemoteEventDispatcher;
import ar.edu.itba.node.Node;
import ar.edu.itba.node.NodeInformation;
import ar.edu.itba.pod.agent.runner.Agent;

@SuppressWarnings("serial")
public class RemoteEventDispatcherImpl extends UnicastRemoteObject implements RemoteEventDispatcher {

	private MyNode currentNode;
	private BlockingQueue<EventInformation> pendingEvents = new LinkedBlockingQueue<EventInformation>();
	private ConcurrentMap<EventInformation, Long> processedEvents = new ConcurrentHashMap<EventInformation, Long>();
	private ConcurrentMap<NodeInformation, Long> updates = new ConcurrentHashMap<NodeInformation, Long>();

	public RemoteEventDispatcherImpl(MyNode currentNode) throws RemoteException {
		super();
		this.currentNode = currentNode;
		try {
			currentNode.getRegistry().bind(Node.DISTRIBUTED_EVENT_DISPATCHER, this);
		} catch (AlreadyBoundException e) {
			System.out.println("El nombre " + Node.DISTRIBUTED_EVENT_DISPATCHER + " ya está bindeado");
			e.printStackTrace();
		}
	}

//	public BlockingQueue<ProcessedEvent> getProcessedEvents(){
//		return this.processedEvents;
//	}

	public void deleteProcessedEvents(){
		synchronized(RemoteEventDispatcher.class){
			Set<EventInformation> events = processedEvents.keySet();
			long currentTime = System.currentTimeMillis();
			for(EventInformation it : events){
				if(it.isOld(currentTime)){
					events.remove(it);
				}
			}
		}
	}

	public boolean noPendingEvents(){
		return this.pendingEvents.isEmpty();
	}

	public EventInformation getHead(){
		synchronized(RemoteEventDispatcher.class){
		try {
			return pendingEvents.take();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		}
		return null;
		}
	}

	public void addProcessedEvents(EventInformation event){
		synchronized(RemoteEventDispatcher.class){
			this.processedEvents.put(event, System.currentTimeMillis());
		}
	}

//	public boolean containsEvent(EventInformation event){
//		synchronized(RemoteEventDispatcherImpl.class){
//		boolean present = false;
//
//		if(pendingEvents != null && !pendingEvents.isEmpty()){
//		for(EventInformation it : pendingEvents){
//			if(it.equals(event))
//				present = true;
//		}
//		}
//		if(!present && processedEvents != null && !processedEvents.isEmpty()){
//			for(ProcessedEvent it : processedEvents){
//				if(it.equals(event))
//					present = true;
//			}
//		}
//
//		return present;
//		}
//	}

	@Override
	public boolean publish(EventInformation event) throws RemoteException, InterruptedException {
//			System.out.println("EVENTO QUE LLEGA " + event.event() + "AGENTE " + event.source());
//			System.out.println("EVENTO QUE LLEGA " + event);
//			System.out.println("EVENTOS PENDIENTES " + pendingEvents);
//			System.out.println("EVENTOS PROCESADOS " + processedEvents);
//			System.out.println(processedEvents.contains(event));
		//	synchronized(RemoteEventDispatcher.class){
			if(!pendingEvents.contains(event) && !processedEvents.containsKey(event)) {
//				System.out.println("EVENTO A PUBLICAR " + event.event() + "AGENTE " + event.source());
				pendingEvents.put(event);

				return true;
			} else {
//				System.out.println("NO PUBLICO " + event.event() + "AGENTE " + event.source());
				return false;
			}
		//	}
	}

	@Override
	public Set<EventInformation> newEventsFor(NodeInformation nodeInformation) throws RemoteException {
		Set<EventInformation> ans = new HashSet<EventInformation>();
		long lastUpdatedTime;
		if(updates.containsKey(nodeInformation))
			lastUpdatedTime = updates.get(nodeInformation);
		else
			lastUpdatedTime = -1;

		long maxTimestamp = lastUpdatedTime;
		//List<ProcessedEvent> myProcessedEvents = new ArrayList<ProcessedEvent>(this.processedEvents);
		
		for(EventInformation it : processedEvents.keySet()){
			long time = processedEvents.get(it); 
			if( time > lastUpdatedTime){
				/*Actualizo el máximo*/
				if(time > maxTimestamp)
					maxTimestamp = time;
			
				ans.add(it);
			}
		}
		updates.put(nodeInformation, maxTimestamp);
		return ans;
	}

	@Override
	public BlockingQueue<Object> moveQueueFor(Agent agent)
			throws RemoteException {
		return currentNode.getDispatcher().deregister(agent);
	}

}
